// NiftyMatrix.m
// By Jayson Adams, NeXT Developer Support Team
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.

#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <appkit/timer.h>
#import <appkit/Cell.h>
#import <appkit/Window.h>
#import <appkit/Application.h>

#import "NewsgroupSetMatrix.h"
#import "Newsgroup.h"
#import "NewsgroupSet.h"
#import "MatrixScroller.h"

@implementation NewsgroupSetMatrix

/* #defines stolen from Draw */

#define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);

#define stopTimer(timer) if (timer) { \
    NXEndTimer(timer); \
    timer = NULL; \
}

#define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK

- initFrame:(const NXRect *)frameRect
{
   [super initFrame:frameRect];
   [self setCellClass:[Newsgroup class]];
   return self;
}

- free
{
    [matrixCache free];
    [cellCache free];
    
    return [super free];
}

- mouseDown:(NXEvent *)theEvent
{
    NXPoint		mouseDownLocation, mouseUpLocation, mouseLocation;
    int			eventMask, row, column, newRow;
    NXRect		visibleRect, cellCacheBounds, cellFrame;
    id			matrixCacheContentView, cellCacheContentView;
    float		dy;
    NXEvent		*event, peek;
    NXTrackingTimer	*timer = NULL;
    BOOL		scrolled = NO;
    
  /* if the Control key isn't down, show normal behavior */
    if (!(theEvent->flags & NX_CONTROLMASK)) {
	return [super mouseDown:theEvent];
    }
    
  /* prepare the cell and matrix cache windows */
    [self setupCacheWindows];
    
  /* we're now interested in mouse dragged events */
    eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];

  /* find the cell that got clicked on and select it */
    mouseDownLocation = theEvent->location;
    [self convertPoint:&mouseDownLocation fromView:nil];
    [self getRow:&row andCol:&column forPoint:&mouseDownLocation];
    activeCell = [self cellAt:row :column];
    //[self selectCell:activeCell];
    [self getCellFrame:&cellFrame at:row :column];
    
  /* do whatever's required for a single-click */
    //[self sendAction];
    
  /* draw a "well" in place of the selected cell (see drawSelf::) */
    [self display:&cellFrame :1];
    
  /* copy what's currently visible into the matrix cache */
    matrixCacheContentView = [matrixCache contentView];
    [matrixCacheContentView lockFocus];
    [self getVisibleRect:&visibleRect];
    [self convertRect:&visibleRect toView:nil];
    PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
    		NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
		[window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY);
    [matrixCacheContentView unlockFocus];

  /* image the cell into its cache */
    cellCacheContentView = [cellCache contentView];
    [cellCacheContentView lockFocus];
    [cellCacheContentView getBounds:&cellCacheBounds];
    //PSsetgray(NX_WHITE);
    //NXRectFill(&cellCacheBounds);
    [activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView];
    [cellCacheContentView unlockFocus];

  /* save the mouse's location relative to the cell's origin */
    dy = mouseDownLocation.y - cellFrame.origin.y;
    
  /* from now on we'll be drawing into ourself */
    [self lockFocus];
    
    event = theEvent;
    while (event->type != NX_MOUSEUP) {
      
      /* erase the active cell using the image in the matrix cache */
	[self getVisibleRect:&visibleRect];
	PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
		    NX_Y(&cellFrame) + NX_Y(&visibleRect) -
		    NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
		    NX_HEIGHT(&cellFrame), [matrixCache gState],
		    NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
		    NX_COPY);
	
      /* move the active cell */
	mouseLocation = event->location;
	[self convertPoint:&mouseLocation fromView:nil];
	cellFrame.origin.y = mouseLocation.y - dy;
	
      /* constrain the cell's location to our bounds */
	if (NX_Y(&cellFrame) < NX_X(&bounds) ) {
	    cellFrame.origin.y = NX_X(&bounds);
	} else if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds)) {
	    cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
	}

      /*
       * make sure the cell will be entirely visible in its new location (if
       * we're in a scrollView, it may not be)
       */
	if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll) {	
	  /*
	   * the cell won't be entirely visible, so scroll, dood, scroll, but
	   * don't display on-screen yet
	   */
	    [window disableFlushWindow];
	    [self scrollRectToVisible:&cellFrame];
	    [window reenableFlushWindow];
	    
	  /* copy the new image to the matrix cache */
	    [matrixCacheContentView lockFocus];
	    [self getVisibleRect:&visibleRect];
	    [self convertRectFromSuperview:&visibleRect];
	    [self convertRect:&visibleRect toView:nil];
	    PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
			NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
			[window gState], 0.0, NX_HEIGHT(&visibleRect),
			NX_COPY);
	    [matrixCacheContentView unlockFocus];
	    
	  /*
	   * note that we scrolled and start generating timer events for
	   * autoscrolling
	   */
	    scrolled = YES;
	    startTimer(timer);
	} else {
	  /* no scrolling, so stop any timer */
	    stopTimer(timer);
	}
      
      /* composite the active cell's image on top of ourself */
	PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
		    [cellCache gState], NX_X(&cellFrame),
		    NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
	
      /* now show what we've done */
	[window flushWindow];
	
      /*
       * if we autoscrolled, flush any lingering window server events to make
       * the scrolling smooth
       */
	if (scrolled) {
	    NXPing();
	    scrolled = NO;
	}
	
      /* save the current mouse location, just in case we need it again */
	mouseLocation = event->location;
	
	if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
	  /*
	   * no mouseMoved or mouseUp event immediately avaiable, so take
	   * mouseMoved, mouseUp, or timer
	   */
	    event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
	} else {
	  /* get the mouseMoved or mouseUp event in the queue */
	    event = [NXApp getNextEvent:MOVE_MASK];
	}
	
      /* if a timer event, mouse location isn't valid, so we'll set it */
	if (event->type == NX_TIMER) {
	    event->location = mouseLocation;
	}
    }
    
  /* mouseUp, so stop any timer and unlock focus */
    stopTimer(timer);
    [self unlockFocus];
    
  /* find the cell under the mouse's location */
    mouseUpLocation = event->location;
    [self convertPoint:&mouseUpLocation fromView:nil];
    if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation]) {
      /* mouse is out of bounds, so find the cell the active cell covers */
	[self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)];
    }
    
  /* we need to shuffle cells if the active cell's going to a new location */
    if (newRow != row) {
      // Update newsgroupSet's newsgroup list
      [target shuffleList:activeCell to:[self cellAt:newRow :0]];

      /* no autodisplay while we move cells around */
	[self setAutodisplay:NO];
	if (newRow > row) {
	  /* adjust selected row if before new active cell location */
	    if (selectedRow <= newRow) {
		selectedRow--;
	    }
	
	  /*
	   * push all cells above the active cell's new location up one row so
	   * that we fill the vacant spot
	   */
	    while (row++ < newRow) {
		cell = [self cellAt:row :0];
		[self putCell:cell at:(row - 1) :0];
	    }
	  /* now place the active cell in its new home */
	    [self putCell:activeCell at:newRow :0];
	} else if (newRow < row) {
          /* adjust selected row if after new active cell location */
	    if (selectedRow >= newRow) {
		selectedRow++;
	    }
	
	  /*
	   * push all cells below the active cell's new location down one row
	   * so that we fill the vacant spot
	   */
	    while (row-- > newRow) {
		cell = [self cellAt:row :0];
		[self putCell:cell at:(row + 1) :0];
	    }
	  /* now place the active cell in its new home */
	    [self putCell:activeCell at:newRow :0];
	}
      
      /* if the active cell is selected, note its new row */
	if ([activeCell state]) {
	    selectedRow = newRow;
	}
      
      /* make sure the active cell's visible if we're autoscrolling */
	if (mFlags.autoscroll) {
	    [self scrollCellToVisible:newRow :0];
	}
      
      /* no longer dragging the cell */
	activeCell = 0;
    
      /* size to cells after all this shuffling and turn autodisplay back on */
	[[self sizeToCells] setAutodisplay:YES];
    } else {
      /* no longer dragging the cell */
	activeCell = 0;
    }
    
  /* now redraw ourself */
    [self display];
    
  /* set the event mask to normal */
    [window setEventMask:eventMask];

    return self;
}

- drawSelf:(NXRect *)rects :(int)count
{
    int		row, col;
    NXRect	cellBorder;
    int		sides[] = {NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
    			   NX_YMIN};
    float	grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE, NX_WHITE, NX_BLACK,
			   NX_BLACK};
			   
  /* do the regular drawing */
    [super drawSelf:rects :count];
    
  /* draw a "well" if the user's dragging a cell */
    if (activeCell) {
      /* get the cell's frame */
	[self getRow:&row andCol:&col ofCell:activeCell];
	[self getCellFrame:&cellBorder at:row :col];
      
      /* draw the well */
	if (NXIntersectsRect(&cellBorder, &(rects[0]))) {
	    NXDrawTiledRects(&cellBorder, (NXRect *)0, sides, grays, 6);
	    PSsetgray(0.17);
	    NXRectFill(&cellBorder);
	}
    }
    
    return self;
}

- setupCacheWindows
{
    NXRect	visibleRect;

  /* create the matrix cache window */
    [self getVisibleRect:&visibleRect];
    matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)];
    
  /* create the cell cache window */
    cellCache = [self sizeCacheWindow:cellCache to:&cellSize];

    return self;
}

- sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
{
    NXRect	cacheFrame;
    
    if (!cacheWindow) {
      /* create the cache window if it doesn't exist */
	cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
	cacheFrame.size = *windowSize;
	cacheWindow = [[[Window alloc] initContent:&cacheFrame
				       style:NX_PLAINSTYLE
				       backing:NX_RETAINED
				       buttonMask:0
				       defer:NO] reenableDisplay];
      /* flip the contentView since we are flipped */
	[[cacheWindow contentView] setFlipped:YES];
    } else {
      /* make sure the cache window's the right size */
	[cacheWindow getFrame:&cacheFrame];
	if (cacheFrame.size.width != windowSize->width ||
      	    cacheFrame.size.height != windowSize->height) {
	    [cacheWindow sizeWindow:windowSize->width
			    	   :windowSize->height];
	}
    }
    
    return cacheWindow;
}

/*
- keyDown:(NXEvent *)theEvent
{
   if(theEvent->data.key.charCode==0x3c){
      [mySet up:self];
      return self;
   }
   if(theEvent->data.key.charCode==0x3e){
      [mySet down:self];
      return self;
   }
   if(theEvent->data.key.charCode==0x20){
      [mySet downOnePage:self];
      return self;
   }
  
   return [super keyDown:theEvent];
}

- (BOOL)acceptsFirstResponder
{
   return TRUE;
}
*/

- (BOOL)selectNewsgroupNamed:(const char *)gname
{
	int i,j;
	
	[self getNumRows:&j numCols:&i];
	for(i=0;i<j;i++)
		if(!strcmp([[self cellAt:i :0] stringValue],gname)){
			[self scrollCellToVisible:i upperOffset:1.5 lowerOffset:1.5];
			[self selectCellAt:i :0];
			[self sendAction];
			return TRUE;
		}
	
	return FALSE;
}

@end
